/*******************************************************************************
 *
 *      Filename: Texture.cpp
 *   Description: See Texture.h
 *        Author: [kb]ypp
 *    Created at: May 27, 2005
 * Last modified: October 19, 2005
 *
 ******************************************************************************/

#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>

#include "Texture.h"

/*extern "C" {
	#include <jpeglib.h>
	#include <png.h>
}*/
/*#include <cstddef> // For size_t
#include <cstdio>
#include <csetjmp> // Try to avoid this! ???
#include <iostream>
using namespace std;*/

#include "Image.h"
#include "Log.h"
#include <cassert>
#include <string>
#include <sstream>
using namespace std;

/*Texture::Texture( int width, int height/ = 1/, int depth/ = 1/,
	GLenum target/ = TEXTURE_2D/,
	int flags/ = RGB | REPEAT | LINEAR_MIPMAP_LINEAR/ )
	: target( target ), flags( flags ),
	  width( width ), height( height ), depth( depth )
{
	glGenTextures( 1, &id );
	glBindTexture( target, id );
	setFlags( flags );
	// Load empty texture image:
	switch ( target ) {
		case TEXTURE_1D:
			glTexImage1D( target, 0, internalFormat, width, 0, format, type, 0 );
			break;
		case TEXTURE_2D:
			glTexImage2D( target, 0, internalFormat, width, height, 0, format, type, 0 );
			break;
		/case TEXTURE_3D:
			// ...
			break;
		case TEXTURE_CUBE:
			// ...
			break;
		case TEXTURE_RECT:
			// ...
			break;/
		default:
			throw TextureError( "Texture::Texture(): unknown texture target" );
	}
}*/

Texture::Texture( GLenum trgt /*= GL_TEXTURE_2D*/ )
	: id( 0 ), target( trgt )
{
	if ( target != GL_TEXTURE_2D &&
		 target != GL_TEXTURE_1D ) {
		LOG_WARNING( "Wrong texture target."
			" Using default value - GL_TEXTURE_2D" );
		target = GL_TEXTURE_2D;
	}

	glGenTextures( 1, &id );
	glBindTexture( target, id );
	if ( glIsTexture( id ) == GL_FALSE )
		LOG_CRITICAL( "glGenTextures() failed" );
}


Texture::Texture( const string &filename,
	GLenum trgt/* = GL_TEXTURE_2D*/,
	int params/* = REPEAT | LINEAR_MIPMAP_LINEAR*/ )
	: id( 0 ), target( trgt )
{
	if ( target != GL_TEXTURE_2D &&
		 target != GL_TEXTURE_1D ) {
		LOG_WARNING( "Wrong texture target."
			" Using default value - GL_TEXTURE_2D" );
		target = GL_TEXTURE_2D;
	}

	glGenTextures( 1, &id );
	glBindTexture( target, id );
	if ( glIsTexture( id ) == GL_FALSE )
		LOG_CRITICAL( "glGenTextures() failed" );
	else
		load( filename, params );
}

Texture::~Texture()
{
	glDeleteTextures( 1, &id );
}

void Texture::load( const string &filename,
	int params/* = REPEAT | LINEAR_MIPMAP_LINEAR*/ )
{
	Image img( filename );
	GLenum format = GL_RGB;
	switch ( img.format() ) {
		case Image::eLuminance:
			format = GL_LUMINANCE;
			break;
		case Image::eLuminanceAlpha:
			format = GL_LUMINANCE_ALPHA;
			break;
		case Image::eRGB:
			format = GL_RGB;
			break;
		case Image::eRGBA:
			format = GL_RGBA;
			break;
		default:
			//LOG_WARNING( "Texure::load(): Image not loaded" );
			return;
	}

	glBindTexture( target, id );
	setParameters( params );
	switch ( target ) {
		case GL_TEXTURE_1D:
			glTexImage1D( GL_TEXTURE_1D, 0, format, img.width(), 0,
				format, GL_UNSIGNED_BYTE, img.data() );
			break;
		case GL_TEXTURE_2D:
		//case GL_TEXTURE_RECTANGLE_ARB:
			glTexImage2D( GL_TEXTURE_2D, 0, format, img.width(), img.height(), 0,
				format, GL_UNSIGNED_BYTE, img.data() );
			break;
		/*case GL_TEXTURE_3D:
			// ...
			break;
		case GL_TEXTURE_CUBE:
			// ...
			break;*/
		default: // Should never get here (only programming errors!)
			{
				ostringstream s;
				s << target;
				LOG_DEBUG( s.str() );
			}
			assert( 0 );
	};
}

/*
unsigned char* Texture::read( const string &fname )
{
	size_t i = fname.rfind( ".jpg", fname.length() - 4 ); // Note: lower case search
	if ( i == fname.length() - 4 ) // last 4 characters
		return read_jpeg( fname );
	i = fname.rfind( ".png", fname.length() - 4 ); // Note: lower case search
	if ( i == fname.length() - 4 ) // last 4 characters
		return read_png( fname );
	// If we get here, than we have a trouble:
	throw TextureError( "Texture::read(): unsupported image file type" );
	return 0;
}

// We need this for libjpeg error handling
struct myErrorMgr {
	struct jpeg_error_mgr pub;
	jmp_buf setjmpBuffer;
};

typedef struct myErrorMgr* myErrorPtr;

void myErrorExit( j_common_ptr cinfo )
{
	myErrorPtr myErr = ( myErrorPtr ) cinfo->err;
	( *cinfo->err->output_message ) ( cinfo );
	longjmp( myErr->setjmpBuffer, 1 );
}

unsigned char* Texture::read_jpeg( const std::string &fname )
{
	FILE *srcFile = fopen( fname.c_str(), "rb" );
	if ( srcFile == 0 ) {
		string err( "Texture::read_jpeg(): could'n open file " );
		err += fname;
		throw TextureError( err.c_str() );
	}
	struct myErrorMgr jerr;
	struct jpeg_decompress_struct cinfo;
	cinfo.err = jpeg_std_error( &jerr.pub );
	jerr.pub.error_exit = myErrorExit;
	if ( setjmp( jerr.setjmpBuffer ) ) {
		// An error occured
		jpeg_destroy_decompress( &cinfo );
		fclose( srcFile );
		throw TextureError( "Texture::read_jpeg(): libjpeg error" );
		// ?!?!?! Should I do something with memory pointed to by data?
		return 0;
	}
	jpeg_create_decompress( &cinfo );
	jpeg_stdio_src( &cinfo, srcFile );
	jpeg_read_header( &cinfo, true );
	// here I can set parameters for decompresion
	// ...
	jpeg_start_decompress( &cinfo );
	width = cinfo.output_width;
	height = cinfo.output_height;
	switch ( cinfo.output_components ) {
		case 1:
			format = GL_LUMINANCE;
			break;
		case 3:
			format = GL_RGB;
			break;
		default:
			throw TextureError( "Texture::read_jpeg(): unknown format" );
	}
	internalFormat = format;
	int rowStride  = width * cinfo.output_components;
	unsigned char *data = new unsigned char[ height * rowStride ];
	if ( data == 0 ) {
		jpeg_destroy_decompress( &cinfo );
		fclose( srcFile );
		throw TextureError( "Texture::read_jpeg(): memory allocation failed" );
	}
	JSAMPARRAY buffer = ( *cinfo.mem->alloc_sarray ) ( ( j_common_ptr ) &cinfo, JPOOL_IMAGE, rowStride, 1 );
	for ( int i = 0; i < height; i++ ) {
		jpeg_read_scanlines( &cinfo, buffer, 1 );
		memcpy( &data[ ( height - i - 1 ) * rowStride ], buffer[ 0 ], rowStride );
	}
	jpeg_finish_decompress( &cinfo );
	jpeg_destroy_decompress( &cinfo );
	fclose( srcFile );

	return data; // WARNING: data MUST be deleted in caller function
}

#ifndef png_jmpbuf
#	define png_jmpbuf( png_ptr ) ( ( png_ptr )->jmpbuf )
#endif

unsigned char* Texture::read_png( const std::string &fname )
{
	FILE *srcFile = fopen( fname.c_str(), "rb" );
	if ( srcFile == 0 ) {
		string err( "Texture::read_png(): could'n open file " );
		err += fname;
		throw TextureError( err.c_str() );
	}
	// Read PNG signature (check if this is a PNG file):
	const unsigned int PNG_BYTES_TO_CHECK = 4;
	png_byte buf[ PNG_BYTES_TO_CHECK ];
	if ( fread( buf, 1, PNG_BYTES_TO_CHECK, srcFile ) != PNG_BYTES_TO_CHECK ) {
		fclose( srcFile );
		string err( "Texture::read_png(): error reading file " );
		err += fname;
		throw TextureError( err.c_str() );
	}
	if ( png_sig_cmp( buf, static_cast< png_size_t >( 0 ), PNG_BYTES_TO_CHECK ) ) {
		fclose( srcFile );
		string err( "Texture::read_png(): file " );
		err += fname + " is not a PNG file";
		throw TextureError( err.c_str() );
	}
	// Initialize PNG structures:
	png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
	if ( png_ptr == 0 ) {
		fclose( srcFile );
		throw TextureError( "Texture::read_png(): failed to create png_struct" );
	}
	png_infop info_ptr = png_create_info_struct( png_ptr );
	if ( png_ptr == 0 ) {
		fclose( srcFile );
		png_destroy_read_struct( &png_ptr, 0, 0 );
		throw TextureError( "Texture::read_png(): failed to create png_info" );
	}
	if ( setjmp( png_jmpbuf( png_ptr ) ) ) {
		// An error occured
		png_destroy_read_struct( &png_ptr, &info_ptr, 0 );
		fclose( srcFile );
		throw TextureError( "Texture::read_jpeg(): libpng error" );
		// ?!?!?! Should I do something with memory pointed to by data?
		return 0;
	}

	png_init_io( png_ptr, srcFile );
	png_set_sig_bytes( png_ptr, PNG_BYTES_TO_CHECK );
	// Extract data from PNG structures:
	png_read_info( png_ptr, info_ptr );
	int bitDepth, colorType;
	png_uint_32 w, h;
	png_get_IHDR( png_ptr, info_ptr, &w, &h, &bitDepth, &colorType, 0, 0, 0 );
	png_set_strip_16( png_ptr );
	png_set_packing( png_ptr );
	if ( bitDepth < 8 )
		png_set_expand( png_ptr );
	switch ( colorType ) {
		case PNG_COLOR_TYPE_PALETTE:
			png_set_expand( png_ptr );
			// or png_set_palette_rgb( png_ptr );
			break;
		case PNG_COLOR_TYPE_GRAY:
		case PNG_COLOR_TYPE_GRAY_ALPHA:
			png_set_gray_to_rgb( png_ptr );
			break;
	}
	if ( png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) )
		png_set_tRNS_to_alpha( png_ptr );
	int intent;
	if ( png_get_sRGB( png_ptr, info_ptr, &intent ) )
		png_set_gamma( png_ptr, 2.2, 0.45455 );
	else {
		double gamma;
		if ( png_get_gAMA( png_ptr, info_ptr, &gamma ) )
			png_set_gamma( png_ptr, 2.2, gamma );
		else
			png_set_gamma( png_ptr, 2.2, 0.45455 );
	}
	png_read_update_info( png_ptr, info_ptr );
	png_get_IHDR( png_ptr, info_ptr, &w, &h, &bitDepth, &colorType, 0, 0, 0 );
	width = w;
	height = h;
	// Read image data:
	png_uint_32 rowBytes = png_get_rowbytes( png_ptr, info_ptr );
	png_uint_32 channels = png_get_channels( png_ptr, info_ptr );
	png_byte *image = new png_byte[ rowBytes * height ];
	png_bytep *rows = new png_bytep[ height ];
	for ( int i = 0; i < height; i++ )
		rows[ i ] = &image[ rowBytes * i ];
	png_read_image( png_ptr, rows );
	// Finish reading:
	png_read_end( png_ptr, info_ptr );
	png_destroy_read_struct( &png_ptr, &info_ptr, 0 );
	fclose( srcFile );
	// Copy image data:
	unsigned char *data = new unsigned char[ width * height * channels ];
	for ( png_uint_32 i = 0; i < rowBytes * h; i++ )
		data[ i ] = image[ i ];
	delete [] rows;
	delete [] image;
	// Select texture format:
	switch ( channels ) {
		case 3:
			format = GL_RGB;
			break;
		case 4:
			format = GL_RGBA;
			break;
		default:
			throw TextureError( "Texture::read_png(): unknown format" );
	}
	internalFormat = format;

	return data;
}*/

void Texture::bind() const
{
	glBindTexture( target, id );
}

void Texture::bind( int unit ) const
{
	glActiveTexture( GL_TEXTURE0 + unit );
	bind();
}

void Texture::enable() const
{
	glEnable( target );
}

void Texture::enable( int unit ) const
{
	glActiveTexture( GL_TEXTURE0 + unit );
	enable();
}

void Texture::disable() const
{
	glDisable( target );
}

void Texture::disable( int unit ) const
{
	glActiveTexture( GL_TEXTURE0 + unit );
	disable();
}

void Texture::setParameters( int params )
{
	// Set texture wrap parameters:
	if ( params & CLAMP ) {
		glTexParameteri( target, GL_TEXTURE_WRAP_S, GL_CLAMP );
		glTexParameteri( target, GL_TEXTURE_WRAP_T, GL_CLAMP );
		glTexParameteri( target, GL_TEXTURE_WRAP_R, GL_CLAMP );
	} else if ( params & CLAMP_TO_EDGE ) {
		glTexParameteri( target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
		glTexParameteri( target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
		glTexParameteri( target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );
	} else if ( params & CLAMP_TO_BORDER ) {
		glTexParameteri( target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
		glTexParameteri( target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );
		glTexParameteri( target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER );
	} else if ( params & REPEAT ) {
		glTexParameteri( target, GL_TEXTURE_WRAP_S, GL_REPEAT );
		glTexParameteri( target, GL_TEXTURE_WRAP_T, GL_REPEAT );
		glTexParameteri( target, GL_TEXTURE_WRAP_R, GL_REPEAT );
	} else if ( params & MIRRORED_REPEAT ) {
		glTexParameteri( target, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT );
		glTexParameteri( target, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT );
		glTexParameteri( target, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT );
	}
	// Set texture filtering parameters:
	if ( params & NEAREST ) {
		glTexParameteri( target, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
		glTexParameteri( target, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
	} else if ( params & LINEAR ) {
		glTexParameteri( target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( target, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	} else if ( params & NEAREST_MIPMAP_NEAREST ) {
		glTexParameteri( target, GL_GENERATE_MIPMAP, GL_TRUE ); // ???!!! Need to test it
		glTexParameteri( target, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
		glTexParameteri( target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST );
	} else if ( params & NEAREST_MIPMAP_LINEAR ) {
		glTexParameteri( target, GL_GENERATE_MIPMAP, GL_TRUE ); // ???!!! Need to test it
		glTexParameteri( target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR );
	} else if ( params & LINEAR_MIPMAP_NEAREST ) {
		glTexParameteri( target, GL_GENERATE_MIPMAP, GL_TRUE ); // ???!!! Need to test it
		glTexParameteri( target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST );
	} else if ( params & LINEAR_MIPMAP_LINEAR ) {
		glTexParameteri( target, GL_GENERATE_MIPMAP, GL_TRUE ); // ???!!! Need to test it
		glTexParameteri( target, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		glTexParameteri( target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	}
}
